/*
 * brin_revmap.c
 *		BRIN 索引的范围映射
 *
 * 范围映射 (revmap) 是 BRIN 索引的翻译结构：对于每个
 * 页面范围，有一个汇总元组，其位置由
 * revmap 跟踪。每当一个新的元组插入到一个表中，违反
 * 先前记录的汇总值时，一个新的元组被插入到索引中，
 * 并且 revmap 被更新以指向它。
 *
 * revmap 存储在索引的前几个页面中，紧随
 * metapage 之后。当 revmap 需要扩展时，所有位于该块的
 * 常规 BRIN 页面上的元组（如果有）都会被移动。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/access/brin/brin_revmap.c
 */
#include "postgres.h"

#include "access/brin_page.h"
#include "access/brin_pageops.h"
#include "access/brin_revmap.h"
#include "access/brin_tuple.h"
#include "access/brin_xlog.h"
#include "access/rmgr.h"
#include "access/xloginsert.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "utils/rel.h"


/*
 * 在 revmap 页中，每个项存储一个 ItemPointerData。这些定义使得人们可以找到给定堆块编号的逻辑 revmap 页面编号和索引编号。
 */
#define HEAPBLK_TO_REVMAP_BLK(pagesPerRange, heapBlk) \
	((heapBlk / pagesPerRange) / REVMAP_PAGE_MAXITEMS)
#define HEAPBLK_TO_REVMAP_INDEX(pagesPerRange, heapBlk) \
	((heapBlk / pagesPerRange) % REVMAP_PAGE_MAXITEMS)


struct BrinRevmap
{
	Relation	rm_irel;
	BlockNumber rm_pagesPerRange;
	BlockNumber rm_lastRevmapPage;	/* 从元页面缓存 */
	Buffer		rm_metaBuf;
	Buffer		rm_currBuf;
};

/* typedef 出现在 brin_revmap.h 中 */


static BlockNumber fc_revmap_get_blkno(BrinRevmap *fc_revmap,
									BlockNumber fc_heapBlk);
static Buffer fc_revmap_get_buffer(BrinRevmap *fc_revmap, BlockNumber fc_heapBlk);
static BlockNumber fc_revmap_extend_and_get_blkno(BrinRevmap *fc_revmap,
											   BlockNumber fc_heapBlk);
static void fc_revmap_physical_extend(BrinRevmap *fc_revmap);

/*
 * 为范围映射初始化访问对象。使用完毕后必须由 brinRevmapTerminate 释放。
 */
BrinRevmap *
brinRevmapInitialize(Relation fc_idxrel, BlockNumber *fc_pagesPerRange,
					 Snapshot fc_snapshot)
{
	BrinRevmap *fc_revmap;
	Buffer		fc_meta;
	BrinMetaPageData *fc_metadata;
	Page		fc_page;

	fc_meta = ReadBuffer(fc_idxrel, BRIN_METAPAGE_BLKNO);
	LockBuffer(fc_meta, BUFFER_LOCK_SHARE);
	fc_page = BufferGetPage(fc_meta);
	TestForOldSnapshot(fc_snapshot, fc_idxrel, fc_page);
	fc_metadata = (BrinMetaPageData *) PageGetContents(fc_page);

	fc_revmap = palloc(sizeof(BrinRevmap));
	fc_revmap->rm_irel = fc_idxrel;
	fc_revmap->rm_pagesPerRange = fc_metadata->pagesPerRange;
	fc_revmap->rm_lastRevmapPage = fc_metadata->lastRevmapPage;
	fc_revmap->rm_metaBuf = fc_meta;
	fc_revmap->rm_currBuf = InvalidBuffer;

	*fc_pagesPerRange = fc_metadata->pagesPerRange;

	LockBuffer(fc_meta, BUFFER_LOCK_UNLOCK);

	return fc_revmap;
}

/*
 * 释放与 revmap 访问对象相关的资源。
 */
void brinRevmapTerminate(BrinRevmap *fc_revmap)
{
	ReleaseBuffer(fc_revmap->rm_metaBuf);
	if (fc_revmap->rm_currBuf != InvalidBuffer)
		ReleaseBuffer(fc_revmap->rm_currBuf);
	pfree(fc_revmap);
}

/*
 * 扩展 revmap 以覆盖给定堆块编号。
 */
void brinRevmapExtend(BrinRevmap *fc_revmap, BlockNumber fc_heapBlk)
{
	BlockNumber fc_mapBlk PG_USED_FOR_ASSERTS_ONLY;

	fc_mapBlk = fc_revmap_extend_and_get_blkno(fc_revmap, fc_heapBlk);

	/* 确保我们得到的缓冲区在预期范围内 */
	Assert(fc_mapBlk != InvalidBlockNumber &&
		   fc_mapBlk != BRIN_METAPAGE_BLKNO &&
		   fc_mapBlk <= fc_revmap->rm_lastRevmapPage);
}

/*
 * 准备将条目插入 revmap；所插入条目的 revmap 缓冲区已被锁定并返回。大多数调用者应在此之前调用 brinRevmapExtend，因为如果 revmap 不够长，这个例程不会扩展它。
 *
 * 返回的缓冲区也在 revmap 结构中记录；完成后释放该缓冲区，因此调用者不需要显式释放它。
 */
Buffer brinLockRevmapPageForUpdate(BrinRevmap *fc_revmap, BlockNumber fc_heapBlk)
{
	Buffer		fc_rmBuf;

	fc_rmBuf = fc_revmap_get_buffer(fc_revmap, fc_heapBlk);
	LockBuffer(fc_rmBuf, BUFFER_LOCK_EXCLUSIVE);

	return fc_rmBuf;
}

/*
 * 在给定的 revmap 缓冲区中（由调用者适当锁定），该缓冲区用于每个范围有 pagesPerRange 页的 BRIN 索引，将对应于堆块编号 heapBlk 的元素设置为给定的 TID。
 *
 * 一旦操作完成，调用者必须更新返回缓冲区上的 LSN。
 *
 * 这在常规操作和 WAL 重放期间都使用。
 */
void brinSetHeapBlockItemptr(Buffer fc_buf, BlockNumber fc_pagesPerRange,
						BlockNumber fc_heapBlk, ItemPointerData fc_tid)
{
	RevmapContents *fc_contents;
	ItemPointerData *fc_iptr;
	Page		fc_page;

	/* 正确的页面应已被固定和锁定 */
	fc_page = BufferGetPage(fc_buf);
	fc_contents = (RevmapContents *) PageGetContents(fc_page);
	fc_iptr = (ItemPointerData *) fc_contents->rm_tids;
	fc_iptr += HEAPBLK_TO_REVMAP_INDEX(fc_pagesPerRange, fc_heapBlk);

	if (ItemPointerIsValid(&fc_tid))
		ItemPointerSet(fc_iptr,
					   ItemPointerGetBlockNumber(&fc_tid),
					   ItemPointerGetOffsetNumber(&fc_tid));
	else
		ItemPointerSetInvalid(fc_iptr);
}

/*
 * 获取给定堆块的 BrinTuple。
 *
 * 包含元组的缓冲区被锁定，并在 *buf 中返回。返回的元组指向共享缓冲区，且不得释放；如果调用者希望在释放缓冲区锁后使用它，则必须创建自己的 palloc'ed 副本。作为一种优化，调用者可以在进入时传递一个固定的缓冲区 *buf，这样当下一个元组与前一个元组位于同一页面时，将避免固定-解除固定循环。
 *
 * 如果在给定堆范围内未找到元组，则返回 NULL。在这种情况下，*buf 可能仍会被更新（且必须由调用者释放固定），但它并未被锁定。
 *
 * 输出元组在缓冲区中的偏移量返回在 *off 中，大小返回在 *size 中。
 */
BrinTuple * brinGetTupleForHeapBlock(BrinRevmap *fc_revmap, BlockNumber fc_heapBlk,
						 Buffer *fc_buf, OffsetNumber *fc_off, Size *fc_size, int fc_mode,
						 Snapshot fc_snapshot)
{
	Relation	fc_idxRel = fc_revmap->rm_irel;
	BlockNumber fc_mapBlk;
	RevmapContents *fc_contents;
	ItemPointerData *fc_iptr;
	BlockNumber fc_blk;
	Page		fc_page;
	ItemId		fc_lp;
	BrinTuple  *fc_tup;
	ItemPointerData fc_previptr;

	/* 将堆块编号规范化为范围内的第一个页面 */
	fc_heapBlk = (fc_heapBlk / fc_revmap->rm_pagesPerRange) * fc_revmap->rm_pagesPerRange;

	/*
	 * 计算我们需要的 revmap 页面编号。如果返回 Invalid（即 revmap 页面尚未创建），则请求的页面范围未被总结。
	 */
	fc_mapBlk = fc_revmap_get_blkno(fc_revmap, fc_heapBlk);
	if (fc_mapBlk == InvalidBlockNumber)
	{
		*fc_off = InvalidOffsetNumber;
		return NULL;
	}

	ItemPointerSetInvalid(&fc_previptr);
	for (;;)
	{
		CHECK_FOR_INTERRUPTS();

		if (fc_revmap->rm_currBuf == InvalidBuffer ||
			BufferGetBlockNumber(fc_revmap->rm_currBuf) != fc_mapBlk)
		{
			if (fc_revmap->rm_currBuf != InvalidBuffer)
				ReleaseBuffer(fc_revmap->rm_currBuf);

			Assert(fc_mapBlk != InvalidBlockNumber);
			fc_revmap->rm_currBuf = ReadBuffer(fc_revmap->rm_irel, fc_mapBlk);
		}

		LockBuffer(fc_revmap->rm_currBuf, BUFFER_LOCK_SHARE);

		fc_contents = (RevmapContents *)
			PageGetContents(BufferGetPage(fc_revmap->rm_currBuf));
		fc_iptr = fc_contents->rm_tids;
		fc_iptr += HEAPBLK_TO_REVMAP_INDEX(fc_revmap->rm_pagesPerRange, fc_heapBlk);

		if (!ItemPointerIsValid(fc_iptr))
		{
			LockBuffer(fc_revmap->rm_currBuf, BUFFER_LOCK_UNLOCK);
			return NULL;
		}

		/*
		 * 检查我们在之前的迭代中得到的 TID（如果有），并保存我们从 revmap 中得到的当前 TID；如果我们循环，我们可以进行有效性检查，确保下一个得到的 TID 是不同的。否则，如果 revmap 以某种方式完全损坏，我们可能会陷入无限循环。
		 */
		if (ItemPointerIsValid(&fc_previptr) && ItemPointerEquals(&fc_previptr, fc_iptr))
			ereport(ERROR,
					(errcode(ERRCODE_INDEX_CORRUPTED),
					 errmsg_internal("corrupted BRIN index: inconsistent range map")));
		fc_previptr = *fc_iptr;

		fc_blk = ItemPointerGetBlockNumber(fc_iptr);
		*fc_off = ItemPointerGetOffsetNumber(fc_iptr);

		LockBuffer(fc_revmap->rm_currBuf, BUFFER_LOCK_UNLOCK);

		/* 好的，得到了指向 BrinTuple 应在的位置的指针。获取它。 */
		if (!BufferIsValid(*fc_buf) || BufferGetBlockNumber(*fc_buf) != fc_blk)
		{
			if (BufferIsValid(*fc_buf))
				ReleaseBuffer(*fc_buf);
			*fc_buf = ReadBuffer(fc_idxRel, fc_blk);
		}
		LockBuffer(*fc_buf, fc_mode);
		fc_page = BufferGetPage(*fc_buf);
		TestForOldSnapshot(fc_snapshot, fc_idxRel, fc_page);

		/* 如果我们落在 revmap 页面上，重新开始 */
		if (BRIN_IS_REGULAR_PAGE(fc_page))
		{
			/*
			 * 如果偏移量大于页面中的值，那么可能是范围被并发地反汇总了。只需返回NULL来处理这种情况。
			 */
			if (*fc_off > PageGetMaxOffsetNumber(fc_page))
			{
				LockBuffer(*fc_buf, BUFFER_LOCK_UNLOCK);
				return NULL;
			}

			fc_lp = PageGetItemId(fc_page, *fc_off);
			if (ItemIdIsUsed(fc_lp))
			{
				fc_tup = (BrinTuple *) PageGetItem(fc_page, fc_lp);

				if (fc_tup->bt_blkno == fc_heapBlk)
				{
					if (fc_size)
						*fc_size = ItemIdGetLength(fc_lp);
					/* 找到了！ */
					return fc_tup;
				}
			}
		}

		/*
		 * 没有好运。假设revmap被并发更新了。
		 */
		LockBuffer(*fc_buf, BUFFER_LOCK_UNLOCK);
	}
	/* 未达到，但保持编译器安静 */
	return NULL;
}

/*
 * 删除一个索引元组，将页面范围标记为未汇总。
 *
 * 索引必须以ShareUpdateExclusiveLock模式锁定。
 *
 * 如果调用方应该重试，则返回false。
 */
bool brinRevmapDesummarizeRange(Relation fc_idxrel, BlockNumber fc_heapBlk)
{
	BrinRevmap *fc_revmap;
	BlockNumber fc_pagesPerRange;
	RevmapContents *fc_contents;
	ItemPointerData *fc_iptr;
	ItemPointerData fc_invalidIptr;
	BlockNumber fc_revmapBlk;
	Buffer		fc_revmapBuf;
	Buffer		fc_regBuf;
	Page		fc_revmapPg;
	Page		fc_regPg;
	OffsetNumber fc_revmapOffset;
	OffsetNumber fc_regOffset;
	ItemId		fc_lp;

	fc_revmap = brinRevmapInitialize(fc_idxrel, &fc_pagesPerRange, NULL);

	fc_revmapBlk = fc_revmap_get_blkno(fc_revmap, fc_heapBlk);
	if (!BlockNumberIsValid(fc_revmapBlk))
	{
		/* revmap页面不存在：范围未汇总，我们结束了 */
		brinRevmapTerminate(fc_revmap);
		return true;
	}

	/* 锁定revmap页面，从中获取索引元组指针 */
	fc_revmapBuf = brinLockRevmapPageForUpdate(fc_revmap, fc_heapBlk);
	fc_revmapPg = BufferGetPage(fc_revmapBuf);
	fc_revmapOffset = HEAPBLK_TO_REVMAP_INDEX(fc_revmap->rm_pagesPerRange, fc_heapBlk);

	fc_contents = (RevmapContents *) PageGetContents(fc_revmapPg);
	fc_iptr = fc_contents->rm_tids;
	fc_iptr += fc_revmapOffset;

	if (!ItemPointerIsValid(fc_iptr))
	{
		/* 没有索引元组：范围未汇总，我们结束了 */
		LockBuffer(fc_revmapBuf, BUFFER_LOCK_UNLOCK);
		brinRevmapTerminate(fc_revmap);
		return true;
	}

	fc_regBuf = ReadBuffer(fc_idxrel, ItemPointerGetBlockNumber(fc_iptr));
	LockBuffer(fc_regBuf, BUFFER_LOCK_EXCLUSIVE);
	fc_regPg = BufferGetPage(fc_regBuf);

	/*
	 * 我们只是删除数据，而不是读取它，因此在这里没有必要
	 * TestForOldSnapshot。
	 */

	/* 如果这不再是一个常规页面，告知调用方重新开始 */
	if (!BRIN_IS_REGULAR_PAGE(fc_regPg))
	{
		LockBuffer(fc_revmapBuf, BUFFER_LOCK_UNLOCK);
		LockBuffer(fc_regBuf, BUFFER_LOCK_UNLOCK);
		brinRevmapTerminate(fc_revmap);
		return false;
	}

	fc_regOffset = ItemPointerGetOffsetNumber(fc_iptr);
	if (fc_regOffset > PageGetMaxOffsetNumber(fc_regPg))
		ereport(ERROR,
				(errcode(ERRCODE_INDEX_CORRUPTED),
				 errmsg("corrupted BRIN index: inconsistent range map")));

	fc_lp = PageGetItemId(fc_regPg, fc_regOffset);
	if (!ItemIdIsUsed(fc_lp))
		ereport(ERROR,
				(errcode(ERRCODE_INDEX_CORRUPTED),
				 errmsg("corrupted BRIN index: inconsistent range map")));

	/*
	 * 占位元组仅在未完成的汇总期间出现，并且我们持有ShareUpdateExclusiveLock，
	 * 所以此函数不能与那个并发运行。因此，任何存在的占位元组都是崩溃或中止汇总的残余；
	 * 静默地将它们移除。
	 */

	START_CRIT_SECTION();

	ItemPointerSetInvalid(&fc_invalidIptr);
	brinSetHeapBlockItemptr(fc_revmapBuf, fc_revmap->rm_pagesPerRange, fc_heapBlk,
							fc_invalidIptr);
	PageIndexTupleDeleteNoCompact(fc_regPg, fc_regOffset);
	/* XXX 记录FSM中的自由空间？ */

	MarkBufferDirty(fc_regBuf);
	MarkBufferDirty(fc_revmapBuf);

	if (RelationNeedsWAL(fc_idxrel))
	{
		xl_brin_desummarize fc_xlrec;
		XLogRecPtr	fc_recptr;

		fc_xlrec.pagesPerRange = fc_revmap->rm_pagesPerRange;
		fc_xlrec.heapBlk = fc_heapBlk;
		fc_xlrec.regOffset = fc_regOffset;

		XLogBeginInsert();
		XLogRegisterData((char *) &fc_xlrec, SizeOfBrinDesummarize);
		XLogRegisterBuffer(0, fc_revmapBuf, 0);
		XLogRegisterBuffer(1, fc_regBuf, REGBUF_STANDARD);
		fc_recptr = XLogInsert(RM_BRIN_ID, XLOG_BRIN_DESUMMARIZE);
		PageSetLSN(fc_revmapPg, fc_recptr);
		PageSetLSN(fc_regPg, fc_recptr);
	}

	END_CRIT_SECTION();

	UnlockReleaseBuffer(fc_regBuf);
	LockBuffer(fc_revmapBuf, BUFFER_LOCK_UNLOCK);
	brinRevmapTerminate(fc_revmap);

	return true;
}

/*
 * 给定堆块编号，找到相应的物理revmap块编号并返回。 
 * 如果revmap页面尚未分配，则返回InvalidBlockNumber。
 */
static BlockNumber fc_revmap_get_blkno(BrinRevmap *fc_revmap, BlockNumber fc_heapBlk)
{
	BlockNumber fc_targetblk;

	/* 获取revmap块编号，为metapage块跳过1 */
	fc_targetblk = HEAPBLK_TO_REVMAP_BLK(fc_revmap->rm_pagesPerRange, fc_heapBlk) + 1;

	/* 正常情况：revmap页面已经分配 */
	if (fc_targetblk <= fc_revmap->rm_lastRevmapPage)
		return fc_targetblk;

	return InvalidBlockNumber;
}

/*
 * 获取并返回一个缓冲区，包含给定堆页面的revmap页面。
 * revmap必须已经扩展到覆盖该页面。
 * 返回的缓冲区也记录在revmap结构中；完成此操作会释放缓冲区，
 * 因此调用方不必显式地释放它。
 */
static Buffer fc_revmap_get_buffer(BrinRevmap *fc_revmap, BlockNumber fc_heapBlk)
{
	BlockNumber fc_mapBlk;

	/* 将堆块编号转换为物理索引位置。 */
	fc_mapBlk = fc_revmap_get_blkno(fc_revmap, fc_heapBlk);

	if (fc_mapBlk == InvalidBlockNumber)
		elog(ERROR, "revmap does not cover heap block %u", fc_heapBlk);

	/* 确保我们得到的缓冲区在预期范围内 */
	Assert(fc_mapBlk != BRIN_METAPAGE_BLKNO &&
		   fc_mapBlk <= fc_revmap->rm_lastRevmapPage);

	/*
	 * 从我们需要读取的缓冲区获取。如果我们在访问结构中
	 * 已经有正确的缓冲区，则使用它；否则，释放那个，
	 * （如果有效）并读取我们需要的那个。
	 */
	if (fc_revmap->rm_currBuf == InvalidBuffer ||
		fc_mapBlk != BufferGetBlockNumber(fc_revmap->rm_currBuf))
	{
		if (fc_revmap->rm_currBuf != InvalidBuffer)
			ReleaseBuffer(fc_revmap->rm_currBuf);

		fc_revmap->rm_currBuf = ReadBuffer(fc_revmap->rm_irel, fc_mapBlk);
	}

	return fc_revmap->rm_currBuf;
}

/*
 * 给定堆块编号，找到相应的物理revmap块编号并返回。
 * 如果revmap页面尚未分配，则扩展revmap，直到它被分配。
 */
static BlockNumber fc_revmap_extend_and_get_blkno(BrinRevmap *fc_revmap, BlockNumber fc_heapBlk)
{
	BlockNumber fc_targetblk;

	/* 获取revmap块编号，为metapage块跳过1 */
	fc_targetblk = HEAPBLK_TO_REVMAP_BLK(fc_revmap->rm_pagesPerRange, fc_heapBlk) + 1;

	/* 如果需要，扩展revmap */
	while (fc_targetblk > fc_revmap->rm_lastRevmapPage)
	{
		CHECK_FOR_INTERRUPTS();
		fc_revmap_physical_extend(fc_revmap);
	}

	return fc_targetblk;
}

/*
 * 尝试通过一个页面来扩展revmap。这可能由于多种原因而未能成功；
 * 调用方应该重试，直到获得预期的结果。
 */
static void fc_revmap_physical_extend(BrinRevmap *fc_revmap)
{
	Buffer		fc_buf;
	Page		fc_page;
	Page		fc_metapage;
	BrinMetaPageData *fc_metadata;
	BlockNumber fc_mapBlk;
	BlockNumber fc_nblocks;
	Relation	fc_irel = fc_revmap->rm_irel;
	bool		fc_needLock = !RELATION_IS_LOCAL(fc_irel);

	/*
	 * 锁定metapage。这将锁定revmap的并发扩展，
	 * 但请注意，我们仍然需要抓住关系扩展锁，因为
	 * 另一个后端可以用常规BRIN页面扩展索引。
	 */
	LockBuffer(fc_revmap->rm_metaBuf, BUFFER_LOCK_EXCLUSIVE);
	fc_metapage = BufferGetPage(fc_revmap->rm_metaBuf);
	fc_metadata = (BrinMetaPageData *) PageGetContents(fc_metapage);

	/*
	 * 检查我们缓存的 lastRevmapPage 值是否是最新的；如果不是，更新缓存副本并让调用者重新开始。
	 */
	if (fc_metadata->lastRevmapPage != fc_revmap->rm_lastRevmapPage)
	{
		fc_revmap->rm_lastRevmapPage = fc_metadata->lastRevmapPage;
		LockBuffer(fc_revmap->rm_metaBuf, BUFFER_LOCK_UNLOCK);
		return;
	}
	fc_mapBlk = fc_metadata->lastRevmapPage + 1;

	fc_nblocks = RelationGetNumberOfBlocks(fc_irel);
	if (fc_mapBlk < fc_nblocks)
	{
		fc_buf = ReadBuffer(fc_irel, fc_mapBlk);
		LockBuffer(fc_buf, BUFFER_LOCK_EXCLUSIVE);
		fc_page = BufferGetPage(fc_buf);
	}
	else
	{
		if (fc_needLock)
			LockRelationForExtension(fc_irel, ExclusiveLock);

		fc_buf = ReadBuffer(fc_irel, P_NEW);
		if (BufferGetBlockNumber(fc_buf) != fc_mapBlk)
		{
			/*
			 * 非常罕见的边缘情况：在我们读取其长度后，有人同时扩展了关系。 如果发生这种情况，放弃并让调用者重新开始。 我们将不得不从使用它的地方撤离该页面。
			 */
			if (fc_needLock)
				UnlockRelationForExtension(fc_irel, ExclusiveLock);
			LockBuffer(fc_revmap->rm_metaBuf, BUFFER_LOCK_UNLOCK);
			ReleaseBuffer(fc_buf);
			return;
		}
		LockBuffer(fc_buf, BUFFER_LOCK_EXCLUSIVE);
		fc_page = BufferGetPage(fc_buf);

		if (fc_needLock)
			UnlockRelationForExtension(fc_irel, ExclusiveLock);
	}

	/* 检查它是否是常规块（或空页面） */
	if (!PageIsNew(fc_page) && !BRIN_IS_REGULAR_PAGE(fc_page))
		ereport(ERROR,
				(errcode(ERRCODE_INDEX_CORRUPTED),
				 errmsg("unexpected page type 0x%04X in BRIN index \"%s\" block %u",
						BrinPageType(fc_page),
						RelationGetRelationName(fc_irel),
						BufferGetBlockNumber(fc_buf))));

	/* 如果页面正在使用，撤离并重新启动 */
	if (brin_start_evacuating_page(fc_irel, fc_buf))
	{
		LockBuffer(fc_revmap->rm_metaBuf, BUFFER_LOCK_UNLOCK);
		brin_evacuate_page(fc_irel, fc_revmap->rm_pagesPerRange, fc_revmap, fc_buf);

		/* 让调用者重新开始 */
		return;
	}

	/*
	 * 好吧，我们现在已经锁定了元页面和目标块。 将目标块重新初始化为 revmap 页面，并更新元页面。
	 */
	START_CRIT_SECTION();

	/* rm_tids 数组通过 PageInit 初始化为所有无效 */
	brin_page_init(fc_page, BRIN_PAGETYPE_REVMAP);
	MarkBufferDirty(fc_buf);

	fc_metadata->lastRevmapPage = fc_mapBlk;

	/*
	 * 将 pd_lower 设置在元数据的末尾之后。 这是必不可少的，因为如果不这样做，如果 xlog.c 压缩页面，元数据将丢失。 （我们必须在这里做这件事，因为早于 v11 的 PG 版本没有正确设置元页面的 pd_lower，因此 pg_upgraded 索引可能包含错误的值。）
	 */
	((PageHeader) fc_metapage)->pd_lower =
		((char *) fc_metadata + sizeof(BrinMetaPageData)) - (char *) fc_metapage;

	MarkBufferDirty(fc_revmap->rm_metaBuf);

	if (RelationNeedsWAL(fc_revmap->rm_irel))
	{
		xl_brin_revmap_extend fc_xlrec;
		XLogRecPtr	fc_recptr;

		fc_xlrec.targetBlk = fc_mapBlk;

		XLogBeginInsert();
		XLogRegisterData((char *) &fc_xlrec, SizeOfBrinRevmapExtend);
		XLogRegisterBuffer(0, fc_revmap->rm_metaBuf, REGBUF_STANDARD);

		XLogRegisterBuffer(1, fc_buf, REGBUF_WILL_INIT);

		fc_recptr = XLogInsert(RM_BRIN_ID, XLOG_BRIN_REVMAP_EXTEND);
		PageSetLSN(fc_metapage, fc_recptr);
		PageSetLSN(fc_page, fc_recptr);
	}

	END_CRIT_SECTION();

	LockBuffer(fc_revmap->rm_metaBuf, BUFFER_LOCK_UNLOCK);

	UnlockReleaseBuffer(fc_buf);
}
